#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <cstring>
#include <fstream>
#include <sys/types.h>
#include <cstdarg>

#include <ctime>
#include "LockGuard.hpp"

namespace log_ns
{
    enum // 日志等级
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string LevelToString(int level) // 转换日志等级为字符串
    {
        switch (level)
        {
        case DEBUG:
            return "DEBUG";
        case INFO:
            return "INFO";
        case WARNING:
            return "WARNING";
        case ERROR:
            return "ERROR";
        case FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    struct logmessage
    {
    public:
        std::string _level;
        pid_t id;
        std::string _filename;
        int _filenumber;
        std::string _curr_time;
        std::string _message_info;
    };

#define SCREEN_TYPE 1
#define FILE_TYPE 2

    const std::string glogfile = "./log.txt";
    pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;

    class Log
    {
    private:
        std::string CurrTimeToString()
        {
            time_t now = time(nullptr);
            struct tm *curr_time = localtime(&now);
            char buff[128];
            snprintf(buff, sizeof(buff), "%d-%02d-%02d %02d:%02d:%02d", curr_time->tm_year - 1900, curr_time->tm_mon + 1, curr_time->tm_mday, curr_time->tm_hour, curr_time->tm_min, curr_time->tm_sec);
            return buff;
        }

        void FlushLogToScreen(const logmessage &lg) // 将日志信息输出到显示器文件中
        {
            printf("[%s][%d][%s][%d][%s]:%s \n", lg._level.c_str(), lg.id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());
        }

        void FlushLogToFile(const logmessage &lg) // 将日志信息输出到指定文件中
        {
            std::ofstream out(_logfile, std::ios::app);
            if (!out.is_open())
            {
                return;
            }
            char buff[2048] = {'\0'};
            snprintf(buff, sizeof(buff), "[%s][%d][%s][%d][%s]:%s \n", lg._level.c_str(), lg.id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());
            out.write(buff, sizeof(buff));

            out.close();
        }

    public:
        Log(const std::string &file = glogfile)
            : _logfile(file), _type(SCREEN_TYPE)
        {
        }
        // 打印日志
        void PrintLog(const logmessage &lg)
        {
            LockGuard lockguard(&glock);
            switch (_type)
            {
            case SCREEN_TYPE:
                FlushLogToScreen(lg);
                break;
            case FILE_TYPE:
                FlushLogToFile(lg);
                break;
            }
        }
        // 切换输出方式
        void Enable(int type)
        {
            _type = type;
        }

        // 构造logmessage对象，并打印
        void LogMessage(std::string filename, int filenumber, int level, const char *format, ...) //...表示可变参数列表
        {
            logmessage lg;

            lg._filename = filename;
            lg._filenumber = filenumber;
            lg._level = LevelToString(level);
            // std::cout << lg._level << std::endl;
            lg.id = getpid();
            lg._curr_time = CurrTimeToString();
            // lg._message_info = CurrTimeToString();
            va_list ap;           // 获取所有参数
            va_start(ap, format); // 取出参数包的部分
            char log_info[1024];
            vsnprintf(log_info, sizeof(log_info), format, ap); // 格式转换
            va_end(ap);
            lg._message_info = log_info;
            // 打印这个日志
            PrintLog(lg);
        }

    private:
        int _type;
        std::string _logfile;
    };

    Log lg;
#define LOG(LeveL, Format, ...)                                          \
    do                                                                   \
    {                                                                    \
        lg.LogMessage(__FILE__, __LINE__, LeveL, Format, ##__VA_ARGS__); \
    } while (0)

#define EnableScreen()          \
    do                          \
    {                           \
        lg.Enable(SCREEN_TYPE); \
    } while (0)

#define EnableFile()          \
    do                        \
    {                         \
        lg.Enable(FILE_TYPE); \
    } while (0)
};